package com.gitee.cashzhang27.test.boot.keycloak.configurer;

import cn.hutool.core.io.resource.ClassPathResource;
import java.io.InputStream;
import lombok.AllArgsConstructor;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter;
import org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter;
import org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;

/**
 * KeycloakSecurityConfiguration.
 *
 * @author Cash Zhang
 * @version v1.0
 * @since 2020/03/08 15:33
 */
@Configuration
@EnableWebSecurity
@AllArgsConstructor
@EnableGlobalMethodSecurity(securedEnabled = true)
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class KeycloakWebSecurityConfigurer extends KeycloakWebSecurityConfigurerAdapter {

  @Override
  protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
    final KeycloakAuthenticationProvider provider = super.keycloakAuthenticationProvider();
    provider.setGrantedAuthoritiesMapper(this.grantedAuthoritiesMapper());
    return provider;
  }

  @Override
  protected void configure(final AuthenticationManagerBuilder auth) {
    auth.authenticationProvider(this.keycloakAuthenticationProvider());
  }

  @Override
  protected void configure(final HttpSecurity httpSecurity) throws Exception {

    httpSecurity
        .csrf()
        .requireCsrfProtectionMatcher(this.keycloakCsrfRequestMatcher())
        .and()
        .sessionManagement()
        .sessionAuthenticationStrategy(this.sessionAuthenticationStrategy())
        .and()
        .addFilterBefore(this.keycloakPreAuthActionsFilter(), LogoutFilter.class)
        .addFilterBefore(this.keycloakAuthenticationProcessingFilter(), LogoutFilter.class)
        .addFilterAfter(
            this.keycloakSecurityContextRequestFilter(),
            SecurityContextHolderAwareRequestFilter.class)
        .addFilterAfter(
            this.keycloakAuthenticatedActionsRequestFilter(),
            KeycloakSecurityContextRequestFilter.class)
        .exceptionHandling()
        .authenticationEntryPoint(this.authenticationEntryPoint())
        .and()
        .logout()
        .addLogoutHandler(this.keycloakLogoutHandler())
        .logoutUrl("/logout")
        .permitAll()
        .logoutSuccessUrl("/");

    ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry =
        httpSecurity.authorizeRequests();

    registry.anyRequest().authenticated();
  }

  /**
   * 设置会话策略.
   *
   * @return SessionAuthenticationStrategy
   */
  @Override
  protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
    return new NullAuthenticatedSessionStrategy();
  }

  /**
   * Overrides default keycloak config resolver behaviour (/WEB-INF/keycloak.json) by a simple
   * mechanism. This example loads other-keycloak.json when the parameter use.other is set to true,
   * e.g.: {@code ./gradlew bootRun -Duse.other=true}
   *
   * @return keycloak config resolver
   */
  @Bean
  public KeycloakConfigResolver keycloakConfigResolver() {
    return new KeycloakConfigResolver() {

      private KeycloakDeployment keycloakDeployment;

      @Override
      public KeycloakDeployment resolve(HttpFacade.Request facade) {
        if (this.keycloakDeployment != null) {
          return this.keycloakDeployment;
        }

        ClassPathResource classPathResource = new ClassPathResource("keycloak.json");
        InputStream configInputStream = classPathResource.getStream();

        if (configInputStream == null) {
          throw new RuntimeException(
              "Could not load Keycloak deployment info: " + classPathResource.getAbsolutePath());
        } else {
          this.keycloakDeployment = KeycloakDeploymentBuilder.build(configInputStream);
        }

        return this.keycloakDeployment;
      }
    };
  }

  /**
   * 转换来自Keycloak的角色名称以匹配Spring Security的约定.
   *
   * @return GrantedAuthoritiesMapper
   */
  @Bean
  public GrantedAuthoritiesMapper grantedAuthoritiesMapper() {
    SimpleAuthorityMapper mapper = new SimpleAuthorityMapper();
    mapper.setConvertToUpperCase(true);
    return mapper;
  }

  /**
   * 阻止双重注册.
   *
   * @param filter KeycloakAuthenticationProcessingFilter
   * @return FilterRegistrationBean
   */
  @Bean
  public FilterRegistrationBean<KeycloakAuthenticationProcessingFilter>
      keycloakAuthenticationProcessingFilterRegistrationBean(
          final KeycloakAuthenticationProcessingFilter filter) {
    final FilterRegistrationBean<KeycloakAuthenticationProcessingFilter> registrationBean =
        new FilterRegistrationBean<>(filter);
    registrationBean.setEnabled(false);
    return registrationBean;
  }

  /**
   * 阻止双重注册.
   *
   * @param filter KeycloakPreAuthActionsFilter
   * @return FilterRegistrationBean
   */
  @Bean
  public FilterRegistrationBean<KeycloakPreAuthActionsFilter>
      keycloakPreAuthActionsFilterRegistrationBean(final KeycloakPreAuthActionsFilter filter) {
    final FilterRegistrationBean<KeycloakPreAuthActionsFilter> registrationBean =
        new FilterRegistrationBean<>(filter);
    registrationBean.setEnabled(false);
    return registrationBean;
  }
}
